package com.example.demo.service.impl;

import com.alibaba.fastjson.JSON;
import com.example.demo.Pojo.ChildCount;
import com.example.demo.Pojo.JoinFamily;
import com.example.demo.Pojo.JoinField;
import com.example.demo.constant.MemberType;
import com.example.demo.service.JoinFamilyService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.join.aggregations.Children;
import org.elasticsearch.join.aggregations.JoinAggregationBuilders;
import org.elasticsearch.join.query.JoinQueryBuilders;
import org.elasticsearch.join.query.ParentIdQueryBuilder;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.ScriptedMetric;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;

@Service
public class JoinFamilyServiceImpl implements JoinFamilyService {

    private static final Log log = LogFactory.getLog(JoinFamilyServiceImpl.class);

    @Autowired
    RestHighLevelClient client;

    @Override
    public void addFamilyMember(String parentId, String name, MemberType type) {
        String id = UUID.randomUUID().toString();
        log.info(id);
        JoinFamily member = new JoinFamily();
        member.setName(name);
        member.setLevel(type.getLevel());
        member.setMy_id(id);
        JoinField joinField = new JoinField();
        joinField.setName(type.getType());
        member.setJoin_filed(joinField);
        if (!type.equals(MemberType.GrandParent)) {
            if (StringUtils.isEmpty(parentId)) {
                throw new IllegalStateException("parentId cannot be null");
            }
            joinField.setParent(parentId);
        }
        String source = JSON.toJSONString(member);
        log.info("source: " + source);
        IndexRequest indexRequest = new IndexRequest("my-index-join_family").id(id).source(source, XContentType.JSON)
                .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
        if (!type.equals(MemberType.GrandParent)) {
            indexRequest.routing(parentId);
        }
        try {
            IndexResponse out = client.index(indexRequest, RequestOptions.DEFAULT);
            log.info(out.getId());
        } catch (IOException e) {
            log.error("", e);
        }

    }

    @Override
    public void addParent(String grandPaId, String name) {
        this.addFamilyMember(grandPaId, name, MemberType.Parent);
    }

    @Override
    public void addChild(String parentId, String name) {
        this.addFamilyMember(parentId, name, MemberType.Child);
    }


    @Override
    public void addGrandPa(String name) {
        this.addFamilyMember(null, name, MemberType.GrandParent);
    }

    @Override
    public JoinFamily getFamilyTree(String grandPaId) throws IOException {

        JoinFamily grandPa = this.findFamilyMemberById(grandPaId);
        List<JoinFamily> list = this.getChild(grandPaId,MemberType.Parent);
        list.forEach(pa -> {
            try {
                pa.setChild(this.getChild(pa.getMy_id(),MemberType.Child));
            } catch (IOException e) {
                log.error("", e);
            }
        });
        grandPa.setChild(list);
        return grandPa;

    }

    @Override
    public JoinFamily findFamilyMemberById(String id) throws IOException {
        JoinFamily member=null;
        SearchRequest search= new SearchRequest("my-index-join_family");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder bool= QueryBuilders.boolQuery()
                .must(QueryBuilders.matchQuery("my_id", id));
        searchSourceBuilder.query(bool);
        search.source(searchSourceBuilder);
        SearchResponse response=client.search(search, RequestOptions.DEFAULT);
        SearchHit[] hit=response.getHits().getHits();
        if (hit.length>0){
            String source = hit[0].getSourceAsString();
            log.info(source);
            member=JSON.parseObject(source,JoinFamily.class);
        }
        return member;
    }

    private List<JoinFamily> getChild(String grandPaId, MemberType type) throws IOException {
        List<JoinFamily> list = new ArrayList<>();
        SearchRequest search = new SearchRequest("my-index-join_family");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        ParentIdQueryBuilder build = JoinQueryBuilders.parentId(type.getType(), grandPaId);
        searchSourceBuilder.query(build);
        search.source(searchSourceBuilder);
        SearchResponse response = client.search(search, RequestOptions.DEFAULT);
        response.getHits().forEach(hi ->{
            String source = hi.getSourceAsString();
            log.info(source);
            JoinFamily meber=JSON.parseObject(source,JoinFamily.class);
            list.add(meber);
        });
        return list;
    }

    @Override
    public List<ChildCount> getChildCountAndName(MemberType memberType) throws IOException {
        List<ChildCount> list = new LinkedList<>();
        SearchRequest search = new SearchRequest("my-index-join_family");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchQuery("join_filed", memberType.getType()));
        AggregationBuilder build = AggregationBuilders.terms("parent_agg").field("name")
                .subAggregation(JoinAggregationBuilders.children("child_agg", "child")
                        .subAggregation(AggregationBuilders.scriptedMetric("child_name")
                                .initScript(new Script("state.transactions = []"))
                                .mapScript(new Script("state.transactions.add(doc.name)"))
                                .combineScript(new Script("String profit =''; for (t in state.transactions) { profit += t } return profit"))
                                .reduceScript(new Script("String profit = ''; for (a in states) { profit += a } return profit")))
                );
        searchSourceBuilder.aggregation(build);
        search.source(searchSourceBuilder);
        SearchResponse response = client.search(search, RequestOptions.DEFAULT);
        Map<String, Aggregation> map = response.getAggregations().getAsMap();
        Terms terms = (Terms) map.get("parent_agg");
        terms.getBuckets().forEach(bucket -> {
            ChildCount count = new ChildCount();
            count.setParentName(bucket.getKeyAsString());
//            System.out.println(bucket.getKeyAsString());
            Map<String, Aggregation> subMap = bucket.getAggregations().getAsMap();
            Children children = (Children) subMap.get("child_agg");
            count.setChildCount(children.getDocCount());
//            System.out.println("childCount " + children.getDocCount());
            Map<String, Aggregation> subSubMap = children.getAggregations().getAsMap();
            ScriptedMetric metric = (ScriptedMetric) subSubMap.get("child_name");
            count.setChildren((String) metric.aggregation());
            list.add(count);
//            System.out.println("childName " + metric.aggregation());
        });
        return list;

    }
}
